/*
 * (C) Copyright 2006-2010 Nuxeo SAS (http://nuxeo.com/) and contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Contributors:
 *     bstefanescu
 */
package org.nuxeo.gwt.habyt.demo.server;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpSession;

import org.nuxeo.gwt.habyt.demo.client.Document;
import org.nuxeo.gwt.habyt.invocation.client.Actions;
import org.nuxeo.gwt.habyt.invocation.client.Invocation;
import org.nuxeo.gwt.habyt.invocation.client.InvocationException;
import org.nuxeo.gwt.habyt.invocation.client.InvocationResponse;
import org.nuxeo.gwt.habyt.invocation.client.Notification;
import org.nuxeo.gwt.habyt.invocation.client.ObjectRef;
import org.nuxeo.gwt.habyt.invocation.client.PropertyMap;
import org.nuxeo.gwt.habyt.invocation.client.Referenceable;
import org.nuxeo.gwt.habyt.invocation.server.Action;
import org.nuxeo.gwt.habyt.invocation.server.AnnotatedInvocationHandler;
import org.nuxeo.gwt.habyt.invocation.server.ServerInvoker;

/**
 * A demo (in-memory) document repository - stored in The HttpSession
 * 
 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
 * 
 */
public class DocumentManager extends AnnotatedInvocationHandler {

    private static class Repository {
        protected Map<String, Document> docs = new HashMap<String, Document>();

        protected Map<String, List<Document>> children = new HashMap<String, List<Document>>();

        public Repository() {
            Document root = new Document("");
            docs.put("", root);
            PropertyMap map = new PropertyMap();
            map.put("sys:name", "docs");
            map.put("dc:title", "My Documents");
            create(root, map);
        }

        public synchronized Document create(Referenceable parent,
                PropertyMap map) {
            String parentId = parent.getRef().getId();
            String name = map.getString("sys:name");
            Document doc = new Document(parentId + '/' + name);
            doc.setProperties(map);
            docs.put(doc.getRef().getId(), doc);
            List<Document> list = children.get(parentId);
            if (list == null) {
                list = new ArrayList<Document>();
                children.put(parentId, list);
            }
            list.add(doc);
            return doc;
        }

        /**
         * Delete the document and return its parent
         * 
         * @param doc
         * @return
         */
        public synchronized Document delete(Referenceable doc) {
            ObjectRef ref = doc.getRef();
            if (docs.remove(ref.getId()) != null) {
                // remove all the tree
                List<Document> docs = children.remove(ref.getId());
                if (docs != null) {
                    for (Document d : docs) {
                        delete(d);
                    }
                }
                String id = ref.getId();
                int p = id.lastIndexOf('/');
                if (p > -1) {
                    id = id.substring(0, p);
                }
                return this.docs.get(id);
            }
            return null;
        }

        public synchronized Document udpate(Referenceable ref, PropertyMap map) {
            Document doc = docs.get(ref.getRef().getId());
            if (doc != null) {
                doc.setProperties(map);
                return doc;
            }
            return null;
        }

        public synchronized Document get(Referenceable ref) {
            return docs.get(ref.getRef().getId());
        }

        public synchronized List<Document> getChildren(Referenceable ref) {
            return children.get(ref.getRef().getId());
        }

    }

    protected static Repository getRepository() {
        HttpSession session = ServerInvoker.getRequest().getSession(true);
        Repository repo = (Repository) session.getAttribute(DocumentManager.class.getName());
        if (repo == null) {
            repo = new Repository();
            session.setAttribute(DocumentManager.class.getName(), repo);
        }
        return repo;
    }

    protected RenderingEngine rendering;

    @Override
    public void init(ServletConfig config) {
        rendering = new RenderingEngine(config.getServletContext());
    }

    public Document lookup(String id) throws Exception {
        Document doc = getRepository().docs.get(id);
        if (doc == null) {
            throw new InvocationException("No Such Document: " + id);
        }
        return doc;
    }

    @Action(Actions.VIEW)
    public void render(Invocation invocation, InvocationResponse response)
            throws Exception {
        ObjectRef ref = invocation.getTarget();
        Document doc = lookup(ref.getId());
        String viewId = ref.hasModifier() ? ref.getModifier() : "default";
        HashMap<String, Object> ctx = new HashMap<String, Object>();
        if (ref.hasParameters()) {
            ctx.putAll(ref.getParameters());
        }
        ctx.put("doc", doc);
        response.setContent(rendering.render(ctx, viewId));
    }

    @Action(Actions.GET)
    public void get(Invocation invocation, InvocationResponse response)
            throws Exception {
        Document doc = getRepository().get(invocation.getTarget());
        response.setContent(doc);
    }

    @Action(Actions.CHILDREN)
    public void getChildren(Invocation invocation, InvocationResponse response)
            throws Exception {
        response.setContent((Serializable) getRepository().getChildren(
                invocation.getTarget()));
    }

    @Action(Actions.DELETE)
    public void delete(Invocation invocation, InvocationResponse response)
            throws Exception {
        Repository repo = getRepository();
        Document parent = repo.delete(invocation.getTarget());
        if (parent == null) {
            throw new InvocationException("Cannot delete document: "
                    + invocation.getTarget().getRef());
        }
        response.setOutcome(new Notification(parent, Actions.VIEW));
    }

    @Action(Actions.CREATE)
    public void create(Invocation invocation, InvocationResponse response)
            throws Exception {
        Repository repo = getRepository();
        PropertyMap properties = (PropertyMap) invocation.getContent();
        Document doc = repo.create(invocation.getTarget(), properties);
        response.setContent(doc);
        response.setOutcome(new Notification(doc, Actions.VIEW));
    }

    @Action(Actions.UPDATE)
    public void update(Invocation invocation, InvocationResponse response)
            throws Exception {
        String id = invocation.getTarget().getId();
        PropertyMap properties = (PropertyMap) invocation.getContent();
        Document doc = getRepository().udpate(invocation.getTarget(),
                properties);
        if (doc == null) {
            throw new InvocationException("No Such Document: " + id);
        }
        response.setContent(doc);
        response.setOutcome(new Notification(invocation.getTarget(),
                Actions.VIEW));
    }

}
